﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using System.Windows.Media.Animation;
using System.Windows.Forms;

namespace MapEditor
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region __Properties
        /// <summary>
        /// Position mouse click on Map 's canvas
        /// </summary>
        private Point _positionMouseInCanvas;

        /// <summary>
        /// Index of item to draw
        /// </summary>
        private int _index = 0;

        /// <summary>
        /// Save object before add to List
        /// </summary>
        private Object _selectedObject = null;

        /// <summary>
        /// Name of file map
        /// </summary>
        private string _fileName;

        /// <summary>
        /// List images
        /// Load all images in folder by method GetAllImages()
        /// </summary>
        private List<ImageBrush> ListImages = new List<ImageBrush>();

        /// <summary>
        /// List all objects have drawn on Canvas
        /// </summary>
        private List<Object> ListObjects = new List<Object>();

        /// <summary>
        /// Position of object when it start drag
        /// </summary>
        private Point _start;

        /// <summary>
        /// Left of canvas is moved
        /// </summary>
        private double _left;

        /// <summary>
        /// Top of canvas is moved
        /// </summary>
        private double _top;

        /// <summary>
        /// If mouse left press, _isDown = true
        /// Else _isDown = false
        /// </summary>
        private bool _isDown;

        /// <summary>
        /// If object is dragging, _isDragging = true
        /// Else _isDragging = false
        /// </summary>
        private bool _isDragging;

        /// <summary>
        /// Canvas need drag
        /// </summary>
        private UIElement _originalElement;

        /// <summary>
        /// 
        /// </summary>
        private MyAdorner _overlayElement;

        /// <summary>
        /// ROM of Object
        /// </summary>
        private ROM _rom;
        #endregion

        /// <summary>
        /// Constructor
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            // Get List Images
            ListImages = GetListImages();

            int y = 5;  // Distance bewteen 2 canvas
            int size = ListImages.Count;    // Number object's type in game

            // Draw list objects in Left Canvas
            for (int i = 0; i < size; i++)
            {
                Canvas cv = new Canvas();
                cv.Height = 50;// ListImages[i].ImageSource.Height;
                cv.Width = 50;// ListImages[i].ImageSource.Width;
                cv.Background = ListImages[i];
                Canvas.SetTop(cv, y);

                // Set canvas in column 1 or 2
                if (i % 2 == 0)
                    Canvas.SetLeft(cv, 10);
                else
                {
                    y += (int)cv.Height + 20;
                    Canvas.SetLeft(cv, 80);
                }

                CanvasItems.Children.Add(cv);
            }
            CanvasItems.Height = y;
        }

        #region __Event
        /// <summary>
        /// Mouse down
        /// Create a new canvas and draw it in Map's canvas
        /// </summary>
        private void Map_MouseDown(object sender, MouseButtonEventArgs e)
        {
            // Draw ROM
            if (_index == 0)
            {
                // Select object, remove it from list and draw ROM
                if (_selectedObject != null)
                {
                    ListObjects.Remove(_selectedObject);

                    // Add rom
                    _rom = new ROM(Map);
                    _rom.Canvas_MouseDown(sender, e);
                }
                return;
            }

            // Add new Object in Map
            Canvas cv = new Canvas();
            cv.Height = ListImages[_index].ImageSource.Height;
            cv.Width = ListImages[_index].ImageSource.Width;
            cv.Background = ListImages[_index];

            int y = (int)_positionMouseInCanvas.Y - (int)ListImages[_index].ImageSource.Height / 2;
            int x = (int)_positionMouseInCanvas.X - (int)ListImages[_index].ImageSource.Width / 2;

            Canvas.SetTop(cv, y);
            Canvas.SetLeft(cv, x);

            // Add image canvas of Object in MapCanvas
            Map.Children.Add(cv);

            // Create object and add to List
            _selectedObject = new Object((ID)_index, (int)_positionMouseInCanvas.X, (int)_positionMouseInCanvas.Y, cv);
            ListObjects.Add(_selectedObject);
        }

        /// <summary>
        /// Update _positionMouseInCanvas when Mouse move
        /// </summary>
        private void Map_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            // Drap ROM
            if (_index == 0)
            {
                if (_rom != null)
                {
                    _rom.Canvas_MouseMove(sender, e);
                }
                return;
            }

            // Update Mouse Position
            _positionMouseInCanvas = e.GetPosition(Map);
        }

        /// <summary>
        /// 
        /// </summary>
        private void Map_MouseUp(object sender, MouseButtonEventArgs e)
        {
            // Set ROM for object
            if (_index == 0 && _rom != null)
            {
                _rom.Canvas_MouseUp(sender, e);

                // Set ROM for object
                _selectedObject.Rect = _rom._current;

                // Add object to List
                ListObjects.Add(_selectedObject);

                // Reset selectedObject
                _selectedObject = null;
                Map.Children.Remove(_rom._current);
                _rom = null;
            }

            // Remove image ROM in MapCanvas
            try
            {
                var o = ListObjects.First(x => x.Image == _selectedObject.Image);
                Map.Children.Remove(o.Rect);
            }
            catch (Exception ex) { }
        }

        /// <summary>
        /// Choose Item
        /// </summary>
        private void CanvasItems_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Canvas cv = e.Source as Canvas;

            int size = ListImages.Count;

            for (int i = 0; i < size; i++)
            {
                if (ListImages[i] == cv.Background) _index = i;
            }

            // If _selectedObject is not null, add it in List before draw other object
            //if (_selectedObject != null)
            //{
            //    ListObjects.Add(_selectedObject);
            //}
        }

        /// <summary>
        /// Get _originalElement
        /// </summary>
        private void Map_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (e.Source == Map) return;

            _isDown = true;
            _start = e.GetPosition(Map);
            _originalElement = e.Source as UIElement;

            e.Handled = true;

            // Draw ROM of selectObject
            try
            {
                var o = ListObjects.First(x => x.Image == _originalElement);
                _selectedObject = o;
                Map.Children.Add(o.Rect);
            }
            catch (Exception ex) { }

            DragStarted();
        }

        /// <summary>
        /// Drag object
        /// </summary>
        private void Map_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            if (_isDown)
            {

                if ((_isDragging == false) && ((Math.Abs(e.GetPosition(Map).X - _start.X) > SystemParameters.MinimumHorizontalDragDistance) ||
                    (Math.Abs(e.GetPosition(Map).Y - _start.Y) > SystemParameters.MinimumVerticalDragDistance)))
                {
                    DragStarted();
                }
                if (_isDragging)
                {
                    DragMoved();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void Map_PreviewMouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            if (_isDragging)
                DragFinished();
        }

        /// <summary>
        /// Delete object when right click
        /// </summary>
        private void Map_MouseRightButtonDown(object sender, MouseButtonEventArgs e)
        {
            e.Handled = true;
            UIElement cv = e.Source as UIElement;
            Map.Children.Remove(cv);

            try
            {
                var o = ListObjects.First(x => x.Image == cv);

                if (o != null)
                    ListObjects.Remove(o);
            }
            catch (Exception) { }

        }

        /// <summary>
        /// Move object
        /// </summary>
        private void Window_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (_overlayElement == null) return;
            switch (e.Key)
            {
                case Key.Left: _overlayElement.Left--;
                    break;
                case Key.Right: _overlayElement.Left++;
                    break;
                case Key.Up: _overlayElement.Top--;
                    break;
                case Key.Down: _overlayElement.Top++;
                    break;
                default:
                    break;
            }

        }

        /// <summary>
        /// 
        /// </summary>
        private void Scroll_PreviewKeyDown(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (_overlayElement != null)
                e.Handled = true;
        }
        #endregion

        #region __Menu

        /// <summary>
        /// 
        /// </summary>
        private void mnNew_Click(object sender, RoutedEventArgs e)
        {
            ListObjects.Clear();
            Map.Children.Clear();
            _fileName = "";
            frmNew frm = new frmNew();
            frm.MySetSize = new frmNew.SetMapSize(SetMapSize);
            frm.ShowDialog();

        }

        /// <summary>
        /// 
        /// </summary>
        private void mnOpen_Click(object sender, RoutedEventArgs e)
        {
            OpenFileDialog openFileDialog1 = new OpenFileDialog();

            openFileDialog1.InitialDirectory = "E:\\Source Code\\mariozawa\\MarioGame\\Game\\Resources\\Map";
            openFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
            openFileDialog1.FilterIndex = 1;
            openFileDialog1.RestoreDirectory = true;

            if (openFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                ListObjects.Clear();
                Map.Children.Clear();

                // Code to write the stream goes here.
                using (System.IO.StreamReader file = new StreamReader(openFileDialog1.FileName))
                {
                    string line;
                    while ((line = file.ReadLine()) != null)
                    {
                        if (line == "List Objects") continue;
                        if(line == "Tree Update")
                        {
                            file.Close();
                            return;
                        }

                        string[] array = line.Split(new Char[] {'\t'});

                        if (array.Length == 2)
                        {
                            Map.Width = Convert.ToInt32(array[0]);
                            Map.Height = Convert.ToInt32(array[1]);
                        }
                        else
                        {
                            int id = Convert.ToInt32(array[1]);
                            int x = Convert.ToInt32(array[2]);
                            int y = (int)Map.Height - Convert.ToInt32(array[3]);

                            Canvas cv = new Canvas();
                            cv.Height = ListImages[id].ImageSource.Height;
                            cv.Width = ListImages[id].ImageSource.Width;
                            cv.Background = ListImages[id];
                            Canvas.SetTop(cv, y - ListImages[id].ImageSource.Height / 2);
                            Canvas.SetLeft(cv, x - ListImages[id].ImageSource.Width / 2);
                            Map.Children.Add(cv);

                            Object o = new Object((ID)id, x, y, cv);

                            if(array.Length > 4)
                            {
                                int rx = Convert.ToInt32(array[4]);
                                int ry = Convert.ToInt32(array[5]);
                                int w = Convert.ToInt32(array[6]);
                                int h = Convert.ToInt32(array[7]);

                                Canvas.SetLeft(o.Rect, rx);
                                Canvas.SetTop(o.Rect, ry);
                                o.Rect.Width = w;
                                o.Rect.Height = h;
                            }

                            ListObjects.Add(o);
                        }


                    }
                    file.Close();
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void mnSave_Click(object sender, RoutedEventArgs e)
        {

            SaveFileDialog saveFileDialog1 = new SaveFileDialog();

            saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
            saveFileDialog1.FilterIndex = 1;
            saveFileDialog1.RestoreDirectory = true;

            if (saveFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                _fileName = @saveFileDialog1.FileName;
            }
            else
            {
                return;
            }

            if(_fileName != "")
            {
                // Calculate size Root Node
                int size = CalSizeRootNode(ListObjects);
                System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, size, size);

                // Set Number for objects
                SetNumber(ListObjects);
                // Build Tree Update
                Node RootNodeUpdate = new Node(rect, 1);
                RootNodeUpdate.ListObjects = new List<Object>(ListObjects);

                RootNodeUpdate.BuildTree(true);

                // Build Tree Render
                // Set Number for objects
                SetNumber(ListObjects);
                Node RootNodeRender = new Node(rect, 1);
                RootNodeRender.ListObjects = new List<Object>(ListObjects);

                RootNodeRender.BuildTree(false);

                // Save
                using (System.IO.StreamWriter file = new StreamWriter(_fileName))
                {
                    file.Write(Map.Width + "\t");
                    file.WriteLine(Map.Height);

                    file.WriteLine("List Objects");

                    int n = ListObjects.Count;

                    for (int i = 0; i < n; i++)
                    {
                        Object o = ListObjects[i];
                        int rx = (int)Canvas.GetLeft(o.Rect);
                        int ry = (int)Canvas.GetTop(o.Rect);
                        file.WriteLine(o.Number + "\t" + (int)o.Id + "\t" + o.PosX + "\t" + (size - o.PosY)
                            + "\t" + rx + "\t" + ry + "\t" + (int)o.Rect.Width + "\t" + (int)o.Rect.Height);
                    }

                    file.WriteLine("Tree Update");
                    RootNodeUpdate.Save(file, size);

                    file.WriteLine("Tree Render");
                    RootNodeRender.Save(file, size);

                    file.Close();
                    System.Windows.MessageBox.Show("Save Successful!");
                }
            }
        }
        /// <summary>
        /// 
        /// </summary>
        private void mnSaveAs_Click(object sender, RoutedEventArgs e)
        {
            SaveFileDialog saveFileDialog1 = new SaveFileDialog();

            saveFileDialog1.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
            saveFileDialog1.FilterIndex = 1;
            saveFileDialog1.RestoreDirectory = true;

            if (saveFileDialog1.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                _fileName = @saveFileDialog1.FileName;
                // Calculate size Root Node
                int size = CalSizeRootNode(ListObjects);
                System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, size, size);

                // Set Number for objects
                SetNumber(ListObjects);
                // Build Tree Update
                Node RootNodeUpdate = new Node(rect, 1);
                RootNodeUpdate.ListObjects = new List<Object>(ListObjects);

                RootNodeUpdate.BuildTree(true);

                // Build Tree Render
                // Set Number for objects
                SetNumber(ListObjects);
                Node RootNodeRender = new Node(rect, 1);
                RootNodeRender.ListObjects = new List<Object>(ListObjects);

                RootNodeRender.BuildTree(false);

                // Save
                using (System.IO.StreamWriter file = new StreamWriter(_fileName))
                {
                    file.Write(Map.Width + "\t");
                    file.WriteLine(Map.Height);

                    file.WriteLine("List Objects");

                    int n = ListObjects.Count;

                    for (int i = 0; i < n; i++)
                    {
                        Object o = ListObjects[i];
                        file.WriteLine(o.Number + "\t" + (int)o.Id + "\t" + o.PosX + "\t" + (size - o.PosY));
                    }

                    file.WriteLine("Tree Update");
                    RootNodeUpdate.Save(file, size);

                    file.WriteLine("Tree Render");
                    RootNodeRender.Save(file, size);

                    file.Close();
                    System.Windows.MessageBox.Show("Save Successful!");
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        private void mnExit_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// Draw Lands
        /// </summary>
        private void mnAuto_Click(object sender, RoutedEventArgs e)
        {
            int size = (int)Map.Width / 50;
            for (int i = 0; i < size; i++)
            {
                Canvas cv = new Canvas();
                cv.Height = 50;
                cv.Width = 50;
                cv.Background = ListImages[11];  //2
                Canvas.SetTop(cv, Map.Height - 50);
                Canvas.SetLeft(cv, i * 50);
                Map.Children.Add(cv);
                ListObjects.Add(new Object((ID)11, i * 50 + 25, (int)Map.Height - 25, cv));

            }
        }

        #endregion

        #region __Method
        /// <summary>
        /// Set size map
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void SetMapSize(int width, int height)
        {
            Map.Children.Clear();
            ListObjects.Clear();

            Map.Width = width;
            Map.Height = height;

            Map.Visibility = System.Windows.Visibility.Visible;
        }

        /// <summary>
        /// Get all images in folder
        /// </summary>
        /// <returns></returns>
        private List<ImageBrush> GetListImages()
        {
            List<ImageBrush> result = new List<ImageBrush>();

            foreach (string _fileName in System.IO.Directory.GetFiles(@"Images\"))
            {
                try
                {
                    ImageBrush img = new ImageBrush(new BitmapImage(new Uri(_fileName, UriKind.Relative)));
                    img.Stretch = Stretch.None;
                    result.Add(img);
                }
                catch (Exception e)
                {
                    System.Windows.MessageBox.Show(e.ToString());
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        private void DragStarted()
        {
            _isDragging = true;
            _left = Canvas.GetLeft(_originalElement);
            _top = Canvas.GetTop(_originalElement);

            _overlayElement = new MyAdorner(_originalElement);

            AdornerLayer layer = AdornerLayer.GetAdornerLayer(_originalElement);
            layer.Add(_overlayElement);

            ListObjects.Remove(_selectedObject);
        }

        /// <summary>
        /// 
        /// </summary>
        private void DragMoved()
        {
            Point CurrentPosition = System.Windows.Input.Mouse.GetPosition(Map);

            _overlayElement.Left = CurrentPosition.X - _start.X;
            _overlayElement.Top = CurrentPosition.Y - _start.Y;
        }

        /// <summary>
        /// 
        /// </summary>
        private void DragFinished()
        {
            if (_isDragging)
            {
                AdornerLayer.GetAdornerLayer(_overlayElement.AdornedElement).Remove(_overlayElement);

                Canvas.SetTop(_originalElement, _top + _overlayElement.Top);
                Canvas.SetLeft(_originalElement, _left + _overlayElement.Left);

                if (_selectedObject.Rect.Width == _selectedObject.Image.Width
                    && _selectedObject.Rect.Height == _selectedObject.Image.Height)
                {
                    Canvas.SetLeft(_selectedObject.Rect, _left + _overlayElement.Left);
                    Canvas.SetTop(_selectedObject.Rect, _top + _overlayElement.Top);
                }

                _selectedObject.PosX = (int)(_left + _overlayElement.Left + _selectedObject.Image.Width / 2);
                _selectedObject.PosY = (int)(_top + _overlayElement.Top + _selectedObject.Image.Height / 2);
                ListObjects.Add(_selectedObject);

                _overlayElement = null;
            }
            _isDragging = false;
            _isDown = false;
        }

        /// <summary>
        /// Calculate screen bounding square
        /// </summary>
        private int CalSizeRootNode(List<Object> list)
        {
            double max = 0;

            foreach (Object o in list)
            {
                int x = o.Rectangle.X + o.Rectangle.Width;
                int y = o.Rectangle.Y + o.Rectangle.Height;

                if (x > max)
                {
                    max = x;
                }

                if (y > max)
                {
                    max = y;
                }
            }

            int size = 800;

            while (size < max)
            {
                size = size * 2;
            }

            return size;
        }

        /// <summary>
        /// Set Number for Objects
        /// </summary>
        /// <param name="List"></param>
        private void SetNumber(List<Object> List)
        {
            int n = List.Count;

            for (int i = 0; i < n; i++)
            {
                List[i].Number = i;
            }
        }

        #endregion

        #region __Command
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            Map.Visibility = System.Windows.Visibility.Hidden;
            frmNew frm = new frmNew();
            frm.MySetSize = new frmNew.SetMapSize(SetMapSize);
            frm.ShowDialog();
        }

        private void NewCommand(object sender, ExecutedRoutedEventArgs e)
        {
            mnNew_Click(null, null);
        }

        private void OpenCommand(object sender, ExecutedRoutedEventArgs e)
        {
            mnOpen_Click(null, null);
        }

        private void SaveCommand(object sender, ExecutedRoutedEventArgs e)
        {
            mnSave_Click(null, null);
        }

        private void SaveAsCommand(object sender, ExecutedRoutedEventArgs e)
        {
            mnSaveAs_Click(null, null);
        }
        #endregion
    }

    //=======================================================================================================//

    #region OVERRIDE ADORNER
    /// <summary>
    /// 
    /// </summary>
    public class MyAdorner : Adorner
    {
        private Rectangle _child = null;
        private double _left = 0;
        private double _top = 0;

        public Rect ROM { get; set; }

        public MyAdorner(UIElement adornedElement)
            : base(adornedElement)
        {
            VisualBrush _brush = new VisualBrush(adornedElement);

            _child = new Rectangle();
            _child.Width = adornedElement.RenderSize.Width;
            _child.Height = adornedElement.RenderSize.Height;

            _child.Fill = _brush;
        }

        public double Left
        {
            get
            {
                return _left;
            }
            set
            {
                _left = value;
                UpdatePosition();
            }
        }

        public double Top
        {
            get
            {
                return _top;
            }
            set
            {
                _top = value;
                UpdatePosition();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="drawingContext"></param>
        protected override void OnRender(DrawingContext drawingContext)
        {
            Rect boundRect = new Rect(this.AdornedElement.DesiredSize);
            Pen renderPen = new Pen(new SolidColorBrush(Colors.BlueViolet), 0.5f);

            int d = 800;
            Point A1 = new Point(boundRect.X - d, boundRect.Y);
            Point B1 = new Point(boundRect.X + boundRect.Width + d, boundRect.Y);

            Point A2 = new Point(boundRect.X - d, boundRect.Y + boundRect.Height);
            Point B2 = new Point(boundRect.X + boundRect.Width + d, boundRect.Y + boundRect.Height);

            Point A3 = new Point(boundRect.X, boundRect.Y - d);
            Point B3 = new Point(boundRect.X, boundRect.Y + boundRect.Height + d);

            Point A4 = new Point(boundRect.X + boundRect.Width, boundRect.Y - d);
            Point B4 = new Point(boundRect.X + boundRect.Width, boundRect.Y + boundRect.Height + d);

            drawingContext.DrawLine(renderPen, A1, B1); // top
            drawingContext.DrawLine(renderPen, A2, B2); // bottom
            drawingContext.DrawLine(renderPen, A3, B3); // left
            drawingContext.DrawLine(renderPen, A4, B4); // right
        }

        protected override Size MeasureOverride(Size constraint)
        {
            _child.Measure(constraint);
            return _child.DesiredSize;
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            _child.Arrange(new Rect(finalSize));
            return finalSize;
        }

        protected override Visual GetVisualChild(int _index)
        {
            return _child;
        }

        protected override int VisualChildrenCount
        {
            get
            {
                return 1;
            }
        }

        private void UpdatePosition()
        {
            AdornerLayer adornerLayer = this.Parent as AdornerLayer;
            if (adornerLayer != null)
            {
                adornerLayer.Update(AdornedElement);
            }
        }

        public override GeneralTransform GetDesiredTransform(GeneralTransform transform)
        {
            GeneralTransformGroup result = new GeneralTransformGroup();
            result.Children.Add(base.GetDesiredTransform(transform));
            result.Children.Add(new TranslateTransform(_left, _top));
            return result;
        }
    }
    #endregion
}
